#ifndef PCPS_ARENA_HEADER
#define PCPS_ARENA_HEADER

#include <src/pcps.h>
#include <new>

namespace pcps {

  //--------------------------------------------------------------------------------
  // pcps::Arena -- Class with which threads can dynamically allocate memory without
  //                worrying about interfering with other threads.
  //
  //                Memory is allocated in contiguous blocks of the specified size,
  //                with buffers placed on either end of each block.
  //
  //                The buffers are present to prevent the dynamically allocated
  //                memory of different threads from being close enough to appear
  //                on the same cache line, as this could lead to performance
  //                degradation if one thread writes to (and thus invalidates) a
  //                cache line in use by another thread.
  //--------------------------------------------------------------------------------

  class Arena {

    private:

      const size_t _block_size;
      const size_t _buffer_size;
      std::vector<char *> _blocks;
      size_t _used;
      char * _next_location;

      // units in which memory is allocated (i.e. all allocations are in multiples of page_size)
      static size_t page_size() { return 16; }

    public:

      // constructor
      Arena(const size_t block_size, const size_t buffer_size)
      : _block_size( block_size + pcps::Arena::page_size() - block_size % pcps::Arena::page_size() ),
        _buffer_size( buffer_size + pcps::Arena::page_size() - buffer_size % pcps::Arena::page_size() ),
        _blocks(0),
        _used(block_size+1),
        _next_location(NULL)
      {}

      // destructor, which deallocates the blocks
      ~Arena() {
        pthread_mutex_lock(pcps::alloc_mutex());
        for (int i = 0; i < _blocks.size(); i++)
          delete [] _blocks.at(i);
        pthread_mutex_unlock(pcps::alloc_mutex());
      }

      // Function to allocate memory.  If the current block is too small, a new block is allocated.
      void * allocate(size_t s) {
        s = ( s % pcps::Arena::page_size() == 0 ? s : s + pcps::Arena::page_size() - s % pcps::Arena::page_size() );
        if (_used + s > _block_size) {
          pthread_mutex_lock(pcps::alloc_mutex());
          char * new_block = new char[ 2*_buffer_size + std::max(_block_size, s) ];
          pthread_mutex_unlock(pcps::alloc_mutex());
          _blocks.push_back(new_block);
          _next_location = new_block + _buffer_size;
          _used = 0;
        }
        void * retval = (void *)_next_location;
        _next_location += s;
        _used += s;
        return retval;
      }

      // function to allocate memory for an array of the given type
      template <class T> T * allocate_array(const size_t n) {
        return (T *) this->allocate( n * sizeof(T) );
      }

      // function to allocate memory for an array of the given type and to initilize the elements to value
      template <class T> T * allocate_array(const size_t n, const T & value) {
        T * retval = (T *) this->allocate( n * sizeof(T) );
        for (size_t i = 0; i < n; i++)
          retval[i] = value;
        return retval;
      }

  };

  //-----------------------------------------------------------------------------------------------------
  // pcps::Arena_Object -- A base class that enable its children to make use of Arena memory allocation.
  //
  //                       Classes derived from Arena_Object will have their memory allocattion handled
  //                       by the Arena class instead of the standard allocator when using the new
  //                       and delete operators.
  //
  //                       An example of how this works is as follows.
  //
  //                          class Child : public pcps::Arena_Object {
  //                            // class definition
  //                          };
  //                          
  //                          void example_function() {
  //
  //                            // create an arena with block size 10kB and buffer size 1kB
  //                            pcps::Arena arena(10000, 1000);
  //
  //                            // create a Child, allocating the necessary memory with arena
  //                            Child * c = new(arena) Child();
  //
  //                            // deletion calls the destructor for the Child object, but its memory remains allocated
  //                            delete c;
  //
  //                            // arena goes out of scope and its destructor deallocates the memory
  //                          }
  //
  //-----------------------------------------------------------------------------------------------------

  class Arena_Object {

    public:

      // overload the new operator so that it uses an Arena to allocate memory
      void * operator new  (size_t s, pcps::Arena & a) { return a.allocate(s); }
      void * operator new[](size_t s, pcps::Arena & a) { return a.allocate(s); }

      // overload the delete operator so that it does not deallocate memory (it will still implicitly call the derived class's destructor)
      // the memory will be deallocated when the Arena object used for allocation is destroyed
      void operator delete  (void * p) {}
      void operator delete[](void * p) {}

  };

} // end namespace pcps

#endif
